/*
 * buildin_commands.c
 *
 *  Created on: Dec 14, 2010
 *      Author: cpy.prefers.you@gmail.com
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "buildin_commands.h"
#include "error_handling.h"
#include "clogo.h"
#include "lexer.h"
#include "parser.h"

static const char HelpMessage[] =
{
    "The CLogo project by " AUTHOR " is still at its early stage of\n"
    "development. It's currently available on the followings:\n"
    "\n"
};

static const char *ListOfBuildinCommands[] =
{
    "*", "+", "-", "/", "<", "<=", "<>", "=", ">", ">=", "and", "array",
    "arraytolist", "butfirst", "butlast", "bye", "first", "for", "help",
    "if", "ifelse", "item", "last", "list", "make", "not", "or", "print",
    "repeat", "run", "sentence", "setitem", "show", "thing", "to", "word"
};

static int IsEqual(Symbol_T *Operand1, Symbol_T *Operand2);
static int IsNumber(char *Name);
static SymbolList_T *ListLexicalAnalyze(Symbol_T *List);
static void RejectSymbol(Symbol_T *Symbol, char *CommandName);

static void RejectSymbol(Symbol_T *Symbol, char *CommandName)
{
    switch(Symbol->Type)
    {
        case TYPE_EMPTY:
            Error(ERROR_NOT_ENOUGH_INPUT, CommandName);
            break;
        case TYPE_PROCEDURE:
            Error(ERROR_DO_NOT_OUTPUT, Symbol->Name  + (Symbol->Type == TYPE_NUMBER), CommandName);
            break;
        default:
            Error(ERROR_TYPE_MISMATCH, CommandName, Symbol->Name + (Symbol->Type == TYPE_NUMBER));
            break;
    }
    return;
}

static Symbol_T *GetIndexedItem(Symbol_T *Index, Symbol_T *ListOrArray, char *CommandName)
{
    int index = (int)strtod(Index->Name + 1, NULL);

    SymbolList_T *TempElement = ListOrArray->Element;
    while (index > 1 && TempElement)
    {
        TempElement = TempElement->Next;
        index--;
    }
    if (!TempElement)
        RejectSymbol(Index, CommandName);
    return TempElement->Symbol;
}

static int IsEqual(Symbol_T *Operand1, Symbol_T *Operand2)
{
    if (Operand1->Type != Operand2->Type)
        return 0;
    switch (Operand1->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
        case TYPE_ARRAY:
        case TYPE_LIST:
            return !strcmp(Operand1->Name, Operand2->Name);
            break;
        default:
            return !strcmp(Operand1->Name, Operand2->Name);
            break;
   }

    return 1;
}

static int IsNumber(char *Name)
{
    char *ptr;
    strtod(Name + 1, &ptr);
    return ptr > Name + 1 && *ptr == 0;
}


#define START_OF_OPERATORS

void _add(Symbol_T *Dest, Symbol_T *Source)
{
    double temp = strtod((Dest->Name) + 1, NULL) + strtod((Source->Name) + 1, NULL);
    sprintf(Dest->Name + 1, "%.*G", NUM_PRINT_LEN, temp);
    return;
}

void _sub(Symbol_T *Dest, Symbol_T *Source)
{
    double temp = strtod((Dest->Name) + 1, NULL) - strtod((Source->Name) + 1, NULL);
    sprintf(Dest->Name + 1, "%.*G", NUM_PRINT_LEN, temp);
    return;
}

void _mul(Symbol_T *Dest, Symbol_T *Source)
{
    double temp = strtod((Dest->Name) + 1, NULL) * strtod((Source->Name) + 1, NULL);
    sprintf(Dest->Name + 1, "%.*G", NUM_PRINT_LEN, temp);
    return;
}

void _div(Symbol_T *Dest, Symbol_T *Source)
{
    double temp = strtod((Dest->Name) + 1, NULL) / strtod((Source->Name) + 1, NULL);
    sprintf(Dest->Name + 1, "%.*G", NUM_PRINT_LEN, temp);
    return;
}

void _above(Symbol_T *Dest, Symbol_T *Source)
{
    int temp = strtod((Dest->Name) + 1, NULL) > strtod((Source->Name) + 1, NULL);
    *Dest = *(LookupName(temp ? TRUE : FALSE));
    return;
}

void _above_or_equal(Symbol_T *Dest, Symbol_T *Source)
{
    int temp = strtod((Dest->Name) + 1, NULL) >= strtod((Source->Name) + 1, NULL);
    *Dest = *(LookupName(temp ? TRUE : FALSE));
    return;
}

void _below(Symbol_T *Dest, Symbol_T *Source)
{
    int temp = strtod((Dest->Name) + 1, NULL) < strtod((Source->Name) + 1, NULL);
    *Dest = *(LookupName(temp ? TRUE : FALSE));
    return;
}

void _below_or_equal(Symbol_T *Dest, Symbol_T *Source)
{
    int temp = strtod((Dest->Name) + 1, NULL) <= strtod((Source->Name) + 1, NULL);
    *Dest = *(LookupName(temp ? TRUE : FALSE));
    return;
}

void _equal(Symbol_T *Dest, Symbol_T *Source)
{
    int temp = IsEqual(Source, Dest);
    CopySymbol(&Dest, LookupName(temp ? TRUE : FALSE));
    return;
}

void _not_equal(Symbol_T *Dest, Symbol_T *Source)
{
    int temp = !IsEqual(Source, Dest);
    CopySymbol(&Dest, LookupName(temp ? TRUE : FALSE));
    return;
}

#define END_OF_OPERATORS
#define START_OF_FUNCTIONS

void _and(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Operand1 = NewSymbol();
    GetThing(Token, Operand1);
    if (!MatchType(Operand1, 2, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Operand1, "and");

    Symbol_T *Operand2 = NewSymbol();
    GetThing(Token, Operand2);
    if (!MatchType(Operand2, 2, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Operand2, "and");

    int temp = Operand1->Type == TYPE_BOOLEAN_TRUE && Operand2->Type == TYPE_BOOLEAN_TRUE;
    CopySymbol(&ReturnValue, LookupName(temp ? TRUE : FALSE));

    FreeSymbol(&Operand1);
    FreeSymbol(&Operand2);
    return;
}

void _or(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Operand1 = NewSymbol();
    GetThing(Token, Operand1);
    if (!MatchType(Operand1, 2, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Operand1, "or");

    Symbol_T *Operand2 = NewSymbol();
    GetThing(Token, Operand2);
    if (!MatchType(Operand2, 2, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Operand2, "or");

    int temp = Operand1->Type == TYPE_BOOLEAN_TRUE || Operand2->Type == TYPE_BOOLEAN_TRUE;
    CopySymbol(&ReturnValue, LookupName(temp ? TRUE : FALSE));

    FreeSymbol(&Operand1);
    FreeSymbol(&Operand2);
    return;
}

void _not(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Operand = NewSymbol();
    GetThing(Token, Operand);
    if (!MatchType(Operand, 2, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Operand, "not");

    int temp = !(Operand->Type == TYPE_BOOLEAN_TRUE);
    CopySymbol(&ReturnValue, LookupName(temp ? TRUE : FALSE));

    FreeSymbol(&Operand);
    return;
}

void _array(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Size = NewSymbol();
    GetThing(Token, Size);
    if (Size->Type != TYPE_NUMBER || IS_POSITIVE_INT(Size->Name))
        RejectSymbol(Size, "array");

    memset(ReturnValue, 0, sizeof(Symbol_T));
    ReturnValue->Type = TYPE_ARRAY;

    SymbolList_T **Temp = &(ReturnValue->Element);

    int size = strtod(Size->Name + 1, NULL);
    for (; size; size--)
    {
        *Temp = NewSymbolList();
        (*Temp)->Symbol = NewSymbol();
        (*Temp)->Symbol->Type = TYPE_LIST;
        SetNameForArrayOrList((*Temp)->Symbol, (*Temp)->Symbol->Name);
        Temp = &((*Temp)->Next);
    }

    SetNameForArrayOrList(ReturnValue, ReturnValue->Name);

    FreeSymbol(&Size);
    return;
}

void _arraytolist(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Array = NewSymbol();
    GetThing(Token, Array);
    if (Array->Type != TYPE_ARRAY)
        RejectSymbol(Array, "array");

    Array->Type = TYPE_LIST;
    SetNameForArrayOrList(Array, Array->Name);
    CopySymbol(&ReturnValue, Array);

    FreeSymbol(&Array);
    return;
}

void _list(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Thing1 = NewSymbol();
    GetThing(Token, Thing1);
    if (!IS_DATUM(Thing1))
        RejectSymbol(Thing1, "list");

    Symbol_T *Thing2 = NewSymbol();
    GetThing(Token, Thing2);
    if (!IS_DATUM(Thing2))
        RejectSymbol(Thing2, "list");

    Symbol_T *List = NewSymbol();
    List->Type = TYPE_LIST;
    List->Element = NewSymbolList();
    CopySymbol(&(List->Element->Symbol), Thing1);
    List->Element->Next = NewSymbolList();
    CopySymbol(&(List->Element->Next->Symbol), Thing2);
    SetNameForArrayOrList(List, List->Name);

    CopySymbol(&ReturnValue, List);

    FreeSymbol(&Thing1);
    FreeSymbol(&Thing2);
    FreeSymbol(&List);
    return;
}

void _word(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Word1 = NewSymbol();
    GetThing(Token, Word1);
    if (!MatchType(Word1, 4, TYPE_WORD, TYPE_NUMBER, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Word1, "word");

    Symbol_T *Word2 = NewSymbol();
    GetThing(Token, Word2);
    if (!MatchType(Word2, 4, TYPE_WORD, TYPE_NUMBER, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Word2, "word");

    Symbol_T *Word = NewSymbol();
    strcpy(Word->Name, Word1->Name);
    strcpy(Word->Name + strlen(Word->Name), Word2->Name + 1);

    Symbol_T *Temp = LookupName(Word->Name);
    if (Temp)
        Word->Type = Temp->Type;
    else if (IsNumber(Word->Name))
        Word->Type = TYPE_NUMBER;
    else
        Word->Type = TYPE_WORD;

    CopySymbol(&ReturnValue, Word);

    FreeSymbol(&Word1);
    FreeSymbol(&Word2);
    FreeSymbol(&Word);
    return;
}

void _sentence(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Thing1 = NewSymbol();
    GetThing(Token, Thing1);
    if (!IS_DATUM(Thing1))
        RejectSymbol(Thing1, "sentence");

    Symbol_T *Thing2 = NewSymbol();
    GetThing(Token, Thing2);
    if (!IS_DATUM(Thing2))
        RejectSymbol(Thing2, "sentence");

    Symbol_T *List = NewSymbol();

    if (Thing1->Type == TYPE_LIST)
        CopySymbolList(&(List->Element), Thing1->Element);
    else
    {
        List->Element = NewSymbolList();
        CopySymbol(&(List->Element->Symbol), Thing1);
    }

    SymbolList_T *TempSymbolList = List->Element;
    while (TempSymbolList->Next)
        TempSymbolList = TempSymbolList->Next;

    if (Thing2->Type == TYPE_LIST)
        CopySymbolList(&(TempSymbolList->Next), Thing2->Element);
    else
    {
        TempSymbolList->Next = NewSymbolList();
        CopySymbol(&(TempSymbolList->Next->Symbol), Thing2);
    }

    List->Type = TYPE_LIST;
    SetNameForArrayOrList(List, List->Name);
    CopySymbol(&ReturnValue, List);

    FreeSymbol(&Thing1);
    FreeSymbol(&Thing2);
    FreeSymbol(&List);
    return;
}

void _first(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Thing = NewSymbol();
    GetThing(Token, Thing);

    Symbol_T *Temp = NewSymbol();

    switch (Thing->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
            if (strlen(Thing->Name) < 2)
                RejectSymbol(Thing, "first");
            strncpy(Temp->Name, Thing->Name, 2);
            Temp->Type = IsNumber(Temp->Name) ? TYPE_NUMBER : TYPE_WORD;
            break;
        case TYPE_LIST:
            if (!Thing->Element)
                RejectSymbol(Thing, "first");
            else
                CopySymbol(&Temp, Thing->Element->Symbol);
            break;
        default:
            RejectSymbol(Thing, "first");
            break;
    }

    CopySymbol(&ReturnValue, Temp);

    FreeSymbol(&Temp);
    FreeSymbol(&Thing);
    return;
}

void _butfirst(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Thing = NewSymbol();
    GetThing(Token, Thing);

    Symbol_T *Temp = NewSymbol();

    switch (Thing->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
        {
            if (strlen(Thing->Name) < 2)
                RejectSymbol(Thing, "butfirst");
            Temp->Name[0] = '"';
            strcpy(Temp->Name + 1, Thing->Name + 2);

            Symbol_T *Temp = LookupName(Thing->Name);
            if (Temp)
                Thing->Type = Temp->Type;
            else if (IsNumber(Thing->Name))
                Thing->Type = TYPE_NUMBER;
            else
                Thing->Type = TYPE_WORD;
            break;
        }
        case TYPE_LIST:
            if (!Thing->Element)
                RejectSymbol(Thing, "butfirst");
            else
            {
                Temp->Type = TYPE_LIST;
                CopySymbolList(&(Temp->Element), Thing->Element->Next);
                SetNameForArrayOrList(Temp, Temp->Name);
                break;
            }
        default:
            RejectSymbol(Thing, "butfirst");
            break;
    }

    CopySymbol(&ReturnValue, Temp);

    FreeSymbol(&Thing);
    FreeSymbol(&Temp);
    return;
}

void _last(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Thing = NewSymbol();
    GetThing(Token, Thing);

    Symbol_T *Temp = NewSymbol();

    switch (Thing->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
            if (strlen(Thing->Name) < 2)
                RejectSymbol(Thing, "last");
            Temp->Name[0] = '"';
            Temp->Name[1] = Thing->Name[strlen(Thing->Name) - 1];
            Temp->Type = IsNumber(Temp->Name) ? TYPE_NUMBER : TYPE_WORD;
            break;
        case TYPE_LIST:
            if (!Thing->Element)
                RejectSymbol(Thing, "last");
            else
            {
                SymbolList_T *TempSymbolList = Thing->Element;
                while (TempSymbolList->Next)
                    TempSymbolList = TempSymbolList->Next;
                CopySymbol(&Temp, TempSymbolList->Next->Symbol);
                break;
            }
        default:
            RejectSymbol(Thing, "last");
            break;
    }

    CopySymbol(&ReturnValue, Temp);

    FreeSymbol(&Temp);
    FreeSymbol(&Thing);
    return;
}

void _butlast(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Thing = NewSymbol();
    GetThing(Token, Thing);

    Symbol_T *Temp = NewSymbol();
    switch (Thing->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
        {
            if (strlen(Thing->Name) < 2)
                RejectSymbol(Thing, "butlast");
            strncpy(Temp->Name, Thing->Name, strlen(Thing->Name) - 1);
            Temp->Name[strlen(Thing->Name) - 1] = 0;

            Symbol_T *Temp = LookupName(Thing->Name);
            if (Temp)
                Thing->Type = Temp->Type;
            else if (IsNumber(Thing->Name))
                Thing->Type = TYPE_NUMBER;
            else
                Thing->Type = TYPE_WORD;
            break;
        }
        case TYPE_LIST:
            if (!Thing->Element)
                RejectSymbol(Thing, "butlast");
            else
            {
                SymbolList_T **TempSymbolList = &(Thing->Element);
                while ((*TempSymbolList)->Next)
                    TempSymbolList = &((*TempSymbolList)->Next);
                SymbolList_T *TempSymbolListBackup = *TempSymbolList;
                *TempSymbolList = 0;
                CopySymbolList(&(Temp->Element), Thing->Element);
                Temp->Type = TYPE_LIST;
                SetNameForArrayOrList(Temp, Temp->Name);
                *TempSymbolList = TempSymbolListBackup;
                break;
            }
        default:
            RejectSymbol(Thing, "butlast");
            break;
    }

    CopySymbol(&ReturnValue, Temp);

    FreeSymbol(&Thing);
    FreeSymbol(&Temp);
    return;
}

void _if(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Condition = NewSymbol();
    GetThing(Token, Condition);
    if (!MatchType(Condition, 2, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Condition, "if");

    Symbol_T *Instruction = NewSymbol();
    GetThing(Token, Instruction);
    if (Instruction->Type != TYPE_LIST)
        RejectSymbol(Instruction, "if");

    if (Condition->Type == TYPE_BOOLEAN_TRUE)
    {
        SymbolList_T *SymbolList = &(SymbolList_T){ .Symbol = Instruction, .Next = 0 };
        _run(&SymbolList, ReturnValue);
    }

    FreeSymbol(&Condition);
    FreeSymbol(&Instruction);
    return;
}

void _ifelse(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Condition = NewSymbol();
    GetThing(Token, Condition);
    if (!MatchType(Condition, 2, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(Condition, "ifelse");

    Symbol_T *Instruction1 = NewSymbol();
    GetThing(Token, Instruction1);
    if (Instruction1->Type != TYPE_LIST)
        RejectSymbol(Instruction1, "ifelse");

    Symbol_T *Instruction2 = NewSymbol();
    GetThing(Token, Instruction2);
    if (Instruction1->Type != TYPE_LIST)
        RejectSymbol(Instruction2, "ifelse");

    SymbolList_T *SymbolList;
    if (Condition->Type == TYPE_BOOLEAN_TRUE)
    {
        SymbolList = &(SymbolList_T){ .Symbol = Instruction1, .Next = 0 };
        _run(&SymbolList, ReturnValue);
    }
    else if (Condition->Type == TYPE_BOOLEAN_FALSE)
    {
        SymbolList = &(SymbolList_T){ .Symbol = Instruction2, .Next = 0 };
        _run(&SymbolList, ReturnValue);
    }

    FreeSymbol(&Condition);
    FreeSymbol(&Instruction1);
    FreeSymbol(&Instruction2);

    return;
}

void _thing(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Thing = NewSymbol();
    Symbol_T *Temp;
    GetThing(Token, Thing);

    switch(Thing->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
            Temp = LookupName(Thing->Name);
            if (Temp && Temp->Value)
                CopySymbol(&ReturnValue, Temp->Value);
            else
                Error(ERROR_HAS_NO_VALUE, Thing->Name + 1);
            break;
        case TYPE_ARRAY:
        case TYPE_LIST:
        default:
            RejectSymbol(Thing, "thing");
            break;
    }

    FreeSymbol(&Thing);
    return;
}

static SymbolList_T *ListLexicalAnalyze(Symbol_T *List)
{
    char *_InputPointer = List->Name + 1;
    return LexicalAnalyze(&_InputPointer, "]");
}

void _run(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Instruction = NewSymbol();
    GetThing(Token, Instruction);

    switch (Instruction->Type)
    {
        case TYPE_LIST:
        {
            SymbolList_T *SymbolList = ListLexicalAnalyze(Instruction);
            SymbolList_T *SymbolListBackup = SymbolList;
            Interprete(&SymbolList, ReturnValue);
            FreeSymbolList(&SymbolListBackup);
            break;
        }
        default:
            RejectSymbol(Instruction, "run");
            break;
    }

    FreeSymbol(&Instruction);
    return;
}

void _item(SymbolList_T **Token, Symbol_T *ReturnValue)
{
    Symbol_T *Index = NewSymbol();
    GetThing(Token, Index);
    if (Index->Type != TYPE_NUMBER || IS_POSITIVE_INT(Index->Name))
        RejectSymbol(Index, "item");

    Symbol_T *Thing = NewSymbol();
    GetThing(Token, Thing);

    switch(Thing->Type)
    {
        case TYPE_ARRAY:
        case TYPE_LIST:
        {
            Symbol_T *Temp = GetIndexedItem(Index, Thing, "item");
            CopySymbol(&ReturnValue, Temp);
            break;
        }
        case TYPE_NUMBER:
        case TYPE_WORD:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
            memset(ReturnValue, 0, sizeof(Symbol_T));
            ReturnValue->Name[0] = '"';
            ReturnValue->Name[1] = Thing->Name[(int)strtod(Index->Name, NULL) + 2];
            ReturnValue->Type = isdigit(ReturnValue->Name[1]) ? TYPE_NUMBER : TYPE_WORD;
            break;
        default:
            RejectSymbol(Thing, "item");
            break;
    }

    FreeSymbol(&Index);
    FreeSymbol(&Thing);
}

void UserDefinedProcedure(SymbolList_T **Token, Symbol_T *ProcedureName)
{
    Symbol_T *Procedure = NewSymbol();
    CopySymbol(&Procedure, ProcedureName);

    SymbolList_T *TempSymbolList = Procedure->ParameterList;

    Symbol_T *Parameter = NewSymbol();
    while (TempSymbolList)
    {
        GetThing(Token, Parameter);
        if (!IS_DATUM(Parameter))
            RejectSymbol(Parameter, Procedure->Name);
        CopySymbol(&(TempSymbolList->Symbol->Value), Parameter);
        InsertLocalVariable(TempSymbolList->Symbol);
        TempSymbolList = TempSymbolList->Next;
    }

    Symbol_T *ReturnValue = NewSymbol();
    int RecursiveInterpreteBackup = RecursiveInterprete;
    RecursiveInterprete = 0;
    TempSymbolList = Procedure->ProcedureBody;
    Interprete(&TempSymbolList, ReturnValue);
    RecursiveInterprete = RecursiveInterpreteBackup;

    TempSymbolList = Procedure->ParameterList;
    while (TempSymbolList)
    {
        RemoveLocalVariable(TempSymbolList->Symbol->Name);
        TempSymbolList = TempSymbolList->Next;
    }

    FreeSymbol(&Parameter);
    FreeSymbol(&Procedure);
    FreeSymbol(&ReturnValue);
    return;
}

#define END_OF_FUNCTIONS
#define START_OF_PROCEDURES

void _show(SymbolList_T **Token)
{
    Symbol_T *Thing = NewSymbol();
    SymbolList_T *Element;
    int PrintLNBackup = PrintLN;
    GetThing(Token, Thing);

    switch(Thing->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
            fprintf(OutputStream, "%s", Thing->Name + 1);
            break;
        case TYPE_ARRAY:
            PrintLN = 0;
            fprintf(OutputStream, "{");
            Element = Thing->Element;
            while (Element)
            {
                switch(Element->Symbol->Type)
                {
                    case TYPE_WORD:
                    case TYPE_NUMBER:
                    case TYPE_BOOLEAN_TRUE:
                    case TYPE_BOOLEAN_FALSE:
                    case TYPE_ARRAY:
                    case TYPE_LIST:
                        _show(&Element);
                        break;
                    default:
                        fprintf(OutputStream, "%s", Element->Symbol->Name);
                        Element = Element->Next;
                        break;
                }
                fprintf(OutputStream, "%s", Element ? " " : "");
            }
            fprintf(OutputStream, "}");
            PrintLN = PrintLNBackup;
            break;
        case TYPE_LIST:
            PrintLN = 0;
            fprintf(OutputStream, "[");
            Element = Thing->Element;
            while (Element)
            {
                switch(Element->Symbol->Type)
                {
                    case TYPE_WORD:
                    case TYPE_NUMBER:
                    case TYPE_BOOLEAN_TRUE:
                    case TYPE_BOOLEAN_FALSE:
                    case TYPE_ARRAY:
                    case TYPE_LIST:
                        _show(&Element);
                        break;
                    default:
                        fprintf(OutputStream, "%s", Element->Symbol->Name);
                        Element = Element->Next;
                        break;
                }
                fprintf(OutputStream, "%s", Element ? " " : "");
            }
            fprintf(OutputStream, "]");
            PrintLN = PrintLNBackup;
            break;
        default:
            RejectSymbol(Thing, "show");
            break;
    }
    if (PrintLN)
        fprintf(OutputStream, "\n");

    FreeSymbol(&Thing);
    return;
}

void _print(SymbolList_T **Token)
{
    Symbol_T *Thing = NewSymbol();
    SymbolList_T *Element;
    int PrintLNBackup = PrintLN;
    GetThing(Token, Thing);

    switch(Thing->Type)
    {
        case TYPE_WORD:
        case TYPE_NUMBER:
        case TYPE_BOOLEAN_TRUE:
        case TYPE_BOOLEAN_FALSE:
            fprintf(OutputStream, "%s", Thing->Name + 1);
            break;
        case TYPE_ARRAY:
            PrintLN = 0;
            fprintf(OutputStream, "{");
            Element = Thing->Element;
            while (Element)
            {
                switch(Element->Symbol->Type)
                {
                    case TYPE_WORD:
                    case TYPE_NUMBER:
                    case TYPE_BOOLEAN_TRUE:
                    case TYPE_BOOLEAN_FALSE:
                    case TYPE_ARRAY:
                        _print(&Element);
                        break;
                    case TYPE_LIST:
                        _show(&Element);
                        break;
                    default:
                        fprintf(OutputStream, "%s", Element->Symbol->Name);
                        Element = Element->Next;
                        break;
                }
                fprintf(OutputStream, "%s", Element ? " " : "");
            }
            fprintf(OutputStream, "}");
            PrintLN = PrintLNBackup;
            break;
        case TYPE_LIST:
            PrintLN = 0;
            Element = Thing->Element;
            while (Element)
            {
                switch(Element->Symbol->Type)
                {
                    case TYPE_WORD:
                    case TYPE_NUMBER:
                    case TYPE_BOOLEAN_TRUE:
                    case TYPE_BOOLEAN_FALSE:
                    case TYPE_ARRAY:
                        _print(&Element);
                        break;
                    case TYPE_LIST:
                        _show(&Element);
                        break;
                    default:
                        fprintf(OutputStream, "%s", Element->Symbol->Name);
                        Element = Element->Next;
                        break;
                }
                fprintf(OutputStream, "%s", Element ? " " : "");
            }
            PrintLN = PrintLNBackup;
            break;
        default:
            RejectSymbol(Thing, "print");
            break;
    }

    if (PrintLN)
        fprintf(OutputStream, "\n");

    FreeSymbol(&Thing);
    return;
}

void _setitem(SymbolList_T **Token)
{
    Symbol_T *Index = NewSymbol();
    GetThing(Token, Index);
    if (Index->Type != TYPE_NUMBER || strpbrk(Index->Name, "-.eE"))
        RejectSymbol(Index, "setitem");

    Symbol_T *ArrayName = NewSymbol();
    GetSymbol(Token, ArrayName);
    if (ArrayName->Type != TYPE_DEREF)
        RejectSymbol(ArrayName, "setitem");

    ArrayName->Name[0] = '"';
    Symbol_T *Array = NewSymbol();
    CopySymbol(&Array, LookupName(ArrayName->Name)->Value);

    Symbol_T *Value = NewSymbol();
    GetThing(Token, Value);
    if (!IS_DATUM(Value))
        RejectSymbol(Value, "setitem");

    Symbol_T *Temp = GetIndexedItem(Index, Array, "item");
    CopySymbol(&Temp, Value);
    SetNameForArrayOrList(Array, Array->Name);

    CopySymbol(&(ArrayName->Value), Array);
    UpdateValue(ArrayName);

    FreeSymbol(&Index);
    FreeSymbol(&ArrayName);
    FreeSymbol(&Array);
    FreeSymbol(&Value);
    return;
}

void _to(SymbolList_T **Token)
{
    Symbol_T *ProcedureName = NewSymbol();
    GetSymbol(Token, ProcedureName);
    if (!MatchType(ProcedureName, 3, TYPE_NAME, TYPE_PROCEDURE, TYPE_FUNCTION))
        RejectSymbol(ProcedureName, "to");
    if (ProcedureName->FunctionPointer)
        Error(ERROR_DEFINED, ProcedureName->Name);

    SymbolList_T **TempSymbolList = &(ProcedureName->ParameterList);
    Symbol_T *Parameter;
    while (*Token)
    {
        Parameter = NewSymbol();
        GetSymbol(Token, Parameter);
        if (Parameter->Type != TYPE_DEREF)
            RejectSymbol(Parameter, "to");
        Parameter->Name[0] = '"';

        if (!*TempSymbolList)
            *TempSymbolList = NewSymbolList();

        (*TempSymbolList)->Symbol = Parameter;
        TempSymbolList = &((*TempSymbolList)->Next);
    }

    char *PrimaryPromptBackup = PrimaryPrompt;
    PrimaryPrompt = "> ";

    char *_InputPointer;
    LexerInit(InputStream, &_InputPointer, PrimaryPrompt);
    ProcedureName->ProcedureBody = LexicalAnalyze(&_InputPointer, "end");

    PrimaryPrompt = PrimaryPromptBackup;

    ProcedureName->Type = TYPE_FUNCTION;
    ProcedureName->FunctionPointer = UserDefinedProcedure;
    InsertSymbol(ProcedureName);
    fprintf(OutputStream, "%s defined\n", ProcedureName->Name);

    FreeSymbol(&ProcedureName);
    return;
}

void _repeat(SymbolList_T **Token)
{
    Symbol_T *RepeatNum = NewSymbol();
    Symbol_T *Instruction = NewSymbol();

    GetSymbol(Token, RepeatNum);
    if (RepeatNum->Type != TYPE_NUMBER || IS_POSITIVE_INT(RepeatNum->Name))
        RejectSymbol(RepeatNum, "repeat");

    GetThing(Token, Instruction);
    if (Instruction->Type != TYPE_LIST)
        RejectSymbol(Instruction, "repeat");

    int Repeat = (int)strtod(RepeatNum->Name + 1, NULL);

    SymbolList_T *SymbolList;
    SymbolList_T *SymbolListBackup;
    Symbol_T *TempSymbol = NewSymbol();

    int RecursiveInterpreteBackup = RecursiveInterprete;

    for (; Repeat; Repeat--)
    {
        SymbolList = ListLexicalAnalyze(Instruction);
        SymbolListBackup = SymbolList;
        RecursiveInterprete = 0;
        Interprete(&SymbolList, TempSymbol);
        FreeSymbolList(&SymbolListBackup);
    }

    RecursiveInterprete = RecursiveInterpreteBackup;

    FreeSymbol(&RepeatNum);
    FreeSymbol(&Instruction);
    FreeSymbol(&TempSymbol);
    return;
}

void _for(SymbolList_T **Token)
{
    Symbol_T *ForControl = NewSymbol();
    Symbol_T *Instruction = NewSymbol();

    GetSymbol(Token, ForControl);
    if (ForControl->Type != TYPE_LIST)
        RejectSymbol(ForControl, "for");

    GetThing(Token, Instruction);
    if (Instruction->Type != TYPE_LIST)
        RejectSymbol(Instruction, "for");

    int len = strlen(ForControl->Name);
    ForControl->Name[len + 1] = 0;
    for (; len; len--)
        ForControl->Name[len] = ForControl->Name[len - 1];
    ForControl->Name[1] = '"';

    SymbolList_T *SymbolList = ListLexicalAnalyze(ForControl);
    SymbolList_T *SymbolListBackup = SymbolList;

    for (len = 1; ForControl->Name[len]; len++)
        ForControl->Name[len] = ForControl->Name[len + 1];

    Symbol_T *LoopVar = NewSymbol();
    GetThing(&SymbolList, LoopVar);
    if (MatchType(LoopVar, 3, TYPE_LIST, TYPE_ARRAY, TYPE_EMPTY))
        RejectSymbol(ForControl, "for");

    Symbol_T *LoopStart = NewSymbol();
    GetThing(&SymbolList, LoopStart);
    if (LoopStart->Type != TYPE_NUMBER)
        RejectSymbol(ForControl, "for");

    Symbol_T *LoopEnd = NewSymbol();
    GetThing(&SymbolList, LoopEnd);
    if (LoopEnd->Type != TYPE_NUMBER)
        RejectSymbol(ForControl, "for");

    Symbol_T *LoopStep = NewSymbol();
    GetThing(&SymbolList, LoopStep);
    if (LoopStep->Type != TYPE_NUMBER)
        RejectSymbol(ForControl, "for");

    if (SymbolList)
        RejectSymbol(ForControl, "for");

    FreeSymbolList(&SymbolListBackup);

    CopySymbol(&(LoopVar->Value), LoopStart);
    InsertLocalVariable(LoopVar);
    Symbol_T *TempSymbol = NewSymbol();
    int RecursiveInterpreteBackup = RecursiveInterprete;

    while (strtod((LoopVar->Value->Name) + 1, NULL) <= strtod((LoopEnd->Name) + 1, NULL))
    {
        SymbolList = ListLexicalAnalyze(Instruction);
        SymbolListBackup = SymbolList;
        RecursiveInterprete = 0;
        Interprete(&SymbolList, TempSymbol);
        _add(LoopVar->Value, LoopStep);
        UpdateValue(LoopVar);
        FreeSymbolList(&SymbolListBackup);
    }

    RecursiveInterprete = RecursiveInterpreteBackup;
    RemoveLocalVariable(LoopVar->Name);

    FreeSymbol(&ForControl);
    FreeSymbol(&Instruction);
    FreeSymbol(&LoopVar);
    FreeSymbol(&LoopStart);
    FreeSymbol(&LoopStep);
    FreeSymbol(&LoopEnd);
    FreeSymbol(&TempSymbol);
    return;
}

void _help(SymbolList_T **Token)
{
    fprintf(OutputStream, "\n");
    fprintf(OutputStream, "%s", HelpMessage);
    int i;
    for (i = 0; i < sizeof(ListOfBuildinCommands) / sizeof(*ListOfBuildinCommands); i++)
        fprintf(OutputStream, "%-*s%s", HELP_MESSAGE_WIDTH, ListOfBuildinCommands[i], (i + 1) % 5 ? "" : "\n");
    fprintf(OutputStream, "\n");
    return;
}

void _make(SymbolList_T **Token)
{
    Symbol_T *VarName = NewSymbol();
    Symbol_T *Value = NewSymbol();

    GetThing(Token, VarName);
    if (!MatchType(VarName, 4, TYPE_WORD, TYPE_NUMBER, TYPE_BOOLEAN_TRUE, TYPE_BOOLEAN_FALSE))
        RejectSymbol(VarName, "make");

    GetThing(Token, Value);
    if (!IS_DATUM(Value))
        RejectSymbol(Value, "make");

    CopySymbol(&(VarName->Value), Value);
    UpdateValue(VarName);

    FreeSymbol(&VarName);
    FreeSymbol(&Value);

    return;
}

void _bye(SymbolList_T **Token)
{
    CLogoFinalize();
    return;
}

#define END_OF_PROCEDURES
